//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================
/*
 * Receive file with YMODEM file transfer protocol
 *
 * Note:  This code uses 8 bit checksum which may not be compatible with
 *        some programs claiming to support YMODEM transfers.
 *
 * 1999/11/24  Nicolas Pitre <nico@cam.org>:
 * Integrated into EBSA285's BIOS for ARM Linux.
 *
 */

#include "time.h"
#include "serial.h"
#include "util.h"
#define DebugSerialOutputString(x)
#define DebugSerialOutputHex(x)
#define DebugSerialOutputByte(x)
#define DebugSerialOutputByteInHex(x)
#define DebugLED(x)

#define SERBASE 0x80050014
#define OK 0
#define ERROR (-1)
#define FALSE 0
#define TRUE 1
#define CAN ('X'&037)
#define SOH 1
#define STX 2
#define EOT 4
#define ACK 6
#define NAK 025
#define TIMEOUT (-2)
#define RETRYMAX 15
#define WCEOT (-10)

static unsigned long Bytesleft;
static unsigned int Blklen;
static char secbuf[1024];
static unsigned long *pout;

#define tx(c)  SerialOutputRawByte(c);DebugSerialOutputString("tx");DebugSerialOutputByteInHex(c);DebugSerialOutputByte('\n')

static int rx(int timeout)
{
    char c = '\0';
    unsigned int expire = jiffies + timeout * 100;

    while (jiffies <= expire && !SerialInputByte(&c)) {
    }
    if (jiffies > expire) return TIMEOUT;

    DebugSerialOutputString("rx");
    DebugSerialOutputByteInHex(c);
    DebugSerialOutputByte('\n');

    return c & 0xff;
}

static inline void putsec(char *p, int n)
{
    unsigned long *q = (unsigned long *)p;
    n += 3;
    n >>= 2;
    while (n--) {
        *pout++ = *q++;
    }
}

static inline long str2long(char *str)
{
    int i;
    long result = 0;

    result += (*(str) - '0');
    for (i = 1; *(str + i) != '\0'; i++) {
        result *= 10;
        result += (*(str+i) - '0');
    }
    return result;
}

static int procheader(char *name)
{
    register char *p;
    register char t1;

    Bytesleft = 0x7fffffff;
    t1 = str2long(name);
    if (t1 > 128) t1 = 128;
    p = name + 1 + t1;
    if (*p) {
        Bytesleft = str2long(p);
        DebugSerialOutputString("Bytesleft ");
        DebugSerialOutputHex(Bytesleft);
        DebugSerialOutputByte('\n');
    }
    return OK;
}

static int wcgetsec(char *rxbuf)
{
    int checksum, wcj, firstch;
    char *p;
    int sectcurr, errors;
    for (errors = 0; errors < RETRYMAX; errors++) {
        if ((firstch = rx(5)) == STX) {
            Blklen = 1024;
            goto get2;
        }
        if (firstch == SOH) {
            Blklen = 128;
    get2:
            DebugLED(2);
            sectcurr = rx(2);
            checksum = 0;
            if ((sectcurr + (rx(2))) == 0377) {
                for (p = rxbuf, wcj = Blklen;--wcj >= 0;) {
                    if ((firstch = rx(2)) < 0)
                        goto bilge;
                    checksum += (*p++ = firstch);
                }
                DebugSerialOutputString("One block received\n");
                if ((firstch = rx(2)) < 0)
                    goto bilge;
                if (((checksum - firstch) & 0377) == 0) {
                    DebugSerialOutputString("wcgetsec return");
                    DebugSerialOutputByteInHex(sectcurr);
                    DebugSerialOutputByte('\n');
                    return sectcurr;
                }
            }
        }
        else if (firstch == EOT)
            return WCEOT;
        else if (firstch == CAN)
            return ERROR;
  bilge:
        DebugLED(4);
        while (rx(2) != TIMEOUT);
        tx(NAK);
    }
    return ERROR;
}

static int wcrxpn(char *rpn)
{
    int c;

 et_tu:
    tx(NAK);
    DebugLED(1);
    while ((c = wcgetsec (rpn)) != 0) {
        if (c == WCEOT) {
            tx(ACK);
            rx(2);
            goto et_tu;
        }
        DebugLED(0xb);
        return ERROR;
    }
    DebugLED(0xa);
    tx(ACK);
    return OK;
}

static int wcrx()
{
    int sectnum, sectcurr, sendchar, cblklen;

    DebugSerialOutputString("wcrx\n");
    sectnum = 0;
    sendchar = NAK;
    for (;;) {
        tx(sendchar);
        sectcurr = wcgetsec (secbuf);
        DebugLED(0x9);
        if (sectcurr == ((sectnum + 1) & 0377)) {
            sectnum++;
            cblklen = Bytesleft > Blklen ? Blklen : Bytesleft;
            putsec (secbuf, cblklen);
            if ((Bytesleft -= cblklen) < 0)
                Bytesleft = 0;
            sendchar = ACK;
        }
        else if (sectcurr == (sectnum & 0377))
            sendchar = ACK;
        else if (sectcurr == WCEOT) {
            tx(ACK);
            return OK;
        }
        else if (sectcurr == ERROR)
            return ERROR;
        else
            return ERROR;
    }
}

static int wcreceive()
{
    int i;
    DebugSerialOutputString("wcreceive\n");
    for (;;) {
        if (wcrxpn (secbuf) == ERROR)
            break;
        for (i = 0;i < 128;i++) {
            DebugSerialOutputByteInHex(i);
            DebugSerialOutputByte(' ');
            DebugSerialOutputByteInHex(secbuf[i]);
            DebugSerialOutputByte('\n');
        }
        if (secbuf[0] == 0)
            return OK;
        DebugSerialOutputString("0 check pass\n");
        if (procheader (secbuf) == ERROR || wcrx () == ERROR)
            break;
    }
    return ERROR;
}

int serial_load(u8* address)
{
    int res;

    SerialOutputString( "\nNow send file with y-modem ...\n" );
    DebugLED(0);
    pout = (unsigned long *)address;
    res = wcreceive();

    SerialOutputString( (res == OK) ? "\nTransfer complete.\n" : "\nTransfer failed\n" );
    return res;
}
